using System;
using System.Collections.Generic;
using PoolKit.Strategies.Creation;
using PoolKit.Strategies.Repairing;
using PoolKit.Strategies.ReusePreparation;

namespace PoolKit
{
    /// <summary>
    /// Single access point for all pool-creation scenarios
    /// </summary>
	public static class Pools
	{
	    /// <summary>
	    /// Creates a static pool - that is, a pool that cannot shrink or grow in size.
	    /// </summary>
	    /// <remarks>
	    /// Can be drained.
	    /// </remarks>
	    /// <typeparam name="TResource">Type of resource newly created pool must contain</typeparam>
	    /// <param name="resourcesToPool">Set of resources newly created pool will contain</param>
	    /// <param name="reusePreparationStrategy">Strategy that will prepare every repooled resource for reuse</param>
	    /// <param name="repairStrategy">Strategy that will attempt to repair every broken repooled resource</param>
	    /// <returns>Static pool</returns>
	    /// <exception cref="ArgumentNullException"></exception>
	    private static Pool<TResource> CreateStaticPool<TResource>(
            IEnumerable<TResource> resourcesToPool,
            ReusePreparationStrategy<TResource> reusePreparationStrategy,
            RepairStrategy<TResource> repairStrategy)
            where TResource : class
		{
		    if (resourcesToPool == null) throw new ArgumentNullException("resourcesToPool");
		    if (reusePreparationStrategy == null) throw new ArgumentNullException("reusePreparationStrategy");
	        if (repairStrategy == null) throw new ArgumentNullException("repairStrategy");

	        var resourceCollection = new ResourceCollection<TResource>(resourcesToPool);

	        return new GenericPool<TResource>(resourceCollection, reusePreparationStrategy, repairStrategy,
	                                         new CommandProcessorThreadPoolBased());
		}

	    /// <summary>
        /// Creates a static pool - that is, a pool that cannot shrink or grow in size. It is able of creating new resources, when starved, however.
	    /// </summary>
	    /// <remarks>
	    /// Cannot be drained, but as a static pool it cannot grow,
	    /// so resources created when the pool starves, are never really pooled. Not really a dynamic pool.
	    /// </remarks>
        /// <typeparam name="TResource">Type of resource newly created pool must contain</typeparam>
        /// <param name="resourcesToPool">Set of resources newly created pool will contain</param>
        /// <param name="reusePreparationStrategy">Strategy that will prepare every repooled resource for reuse</param>
        /// <param name="repairStrategy">Strategy that will attempt to repair every broken repooled resource</param>
        /// <param name="resourceCreationStrategy">A strategy to be used for creating new resources when pool is starved</param>
        /// <returns>Static pool that tolerates starvation</returns>
        /// <exception cref="ArgumentNullException"></exception>
	    public static Pool<TResource> CreateStaticPoolToleratingStarvation<TResource>(
            IEnumerable<TResource> resourcesToPool,
            ReusePreparationStrategy<TResource> reusePreparationStrategy,
            RepairStrategy<TResource> repairStrategy,
            ResourceCreationStrategy<TResource> resourceCreationStrategy)
            where TResource : class
        {
            if (resourcesToPool == null) throw new ArgumentNullException("resourcesToPool");
            if (reusePreparationStrategy == null) throw new ArgumentNullException("reusePreparationStrategy");
            if (repairStrategy == null) throw new ArgumentNullException("repairStrategy");
	        if (resourceCreationStrategy == null) throw new ArgumentNullException("resourceCreationStrategy");

            return new PoolToleratingStarvation<TResource>(
                CreateStaticPool(resourcesToPool, reusePreparationStrategy, repairStrategy),
                resourceCreationStrategy);
        }

        /// <summary>
        /// Creates a dynamic pool - one that can shrink or grow due to load. Cannot ever be starved.
        /// </summary>
        /// <typeparam name="TResource"></typeparam>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns></returns>
        public static Pool<TResource> CreateDynamicPool<TResource>(
            IEnumerable<TResource> resourcesToPool,
            ReusePreparationStrategy<TResource> reusePreparationStrategy,
            RepairStrategy<TResource> repairStrategy,
            ResourceCreationStrategy<TResource> creationStrategy,
            int minimumCountOfResources,
            TimeSpan minimumResourceIdleTimeNeededToEvictIt)
            where TResource : class
        {
            if (resourcesToPool == null) throw new ArgumentNullException("resourcesToPool");
            if (reusePreparationStrategy == null) throw new ArgumentNullException("reusePreparationStrategy");
            if (repairStrategy == null) throw new ArgumentNullException("repairStrategy");
            if (creationStrategy == null) throw new ArgumentNullException("creationStrategy");

            if (minimumCountOfResources < 0)
            {
                throw new ArgumentOutOfRangeException("minimumCountOfResources", Resources.ExpectingNonNegativeNumber);
            }

            if (minimumResourceIdleTimeNeededToEvictIt < TimeSpan.Zero)
            {
                throw new ArgumentOutOfRangeException("minimumResourceIdleTimeNeededToEvictIt", Resources.NegativeTimeSpansNotAllowed);
            }

            var resourceCollection = new ResourceCollection<TResource>(resourcesToPool);

            var resourceCollectionUsageTracking = new ResourceCollectionUsageTracking<TResource>(resourceCollection,
                                                                                                 resourcesToPool,
                                                                                                 new StopwatchFactory());

            var creationStrategyThreadSafe = creationStrategy.ToThreadSafe();
            var repairStrategyThreadSafe = repairStrategy.ToThreadSafe();
            var reusePreparationStrategyThreadSafe = reusePreparationStrategy.ToThreadSafe();

            var commandProcessor = new CommandProcessorThreadPoolBased();

            var disposablePoolObservers = new List<IDisposable>();

            if (minimumCountOfResources > 0)
            {
                var collectionMinimumCountEnforcer = new CollectionMinimumCountEnforcer<TResource>(
                    resourceCollectionUsageTracking, commandProcessor, minimumCountOfResources,
                    creationStrategyThreadSafe,
                    1);

                collectionMinimumCountEnforcer.Start();

                disposablePoolObservers.Add(collectionMinimumCountEnforcer);
            }

            int minimumCountOfResourcesForEvictionToKickIn = minimumCountOfResources + 1;

            var evictionManager = new ResourceEvictionManager<TResource>(
                commandProcessor,
                resourceCollectionUsageTracking,
                minimumResourceIdleTimeNeededToEvictIt,
                minimumCountOfResourcesForEvictionToKickIn);

            evictionManager.Start();

            disposablePoolObservers.Add(evictionManager);

            return new DynamicPool<TResource>(
                        new GenericPoolDisposableObserverFriendly<TResource>(
                            resourceCollectionUsageTracking,
                            reusePreparationStrategyThreadSafe,
                            repairStrategyThreadSafe,
                            commandProcessor,
                            disposablePoolObservers),
                        creationStrategyThreadSafe);
        }
	}
}